'''
Searchable Symmetric Encryption Scheme

Python Version: 3.6
Dependency: cryptography module (see README)

Implemented by Kobra Amirizirtol, Daniel Kane, Richard Nelson

Based on the work by Reza Curtmola, Seny Kamara, Juan Garay
and Rafail Ostrovsky in 'Searchable Symmetric Encryption: Improved
Definitions and Efficient Constructions'
可搜索对称加密方案 Python 版本：3.6 依赖：密码学模块（参见自述文件）由 Kobra Amirizirtol、Daniel Kane、Richard Nelson 实现 基于 Reza Curtmola、Seny Kamara、Juan Garay 和 Rafail Ostrovsky 在“可搜索对称加密：改进的定义”中的工作和高效的建筑'
'''
import hashlib
import itertools
import time
from itertools import permutations, combinations  # used for permutations用于排列

import MySQLdb
from cryptography.fernet import Fernet  # used for the symmetric key generation用于对称密钥生成
from collections import Counter  # used to count most common word用来统计最常用的词
from collections import defaultdict  # used to make the the distinct word list用于制作不同的单词列表
from pyllist import dllist, dllistnode  # python linked list librarypython链表库
import base64  # used for base 64 encoding用于 base 64 编码
import os
from cryptography.hazmat.backends import default_backend  # used in making key from password用于从密码制作密钥
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import random  # to select random key选择随机密钥
import sys
import re
import bitarray  # for lookup table对于查找表

import cuttxt


def main():
    # print("欢迎使用可搜索的对称加密!")
    # print('************************************************************************************')
    # reply = input("您是否已有加密数据集? (Y)es or (N)o: ")
    while (True):
        # # if yes then you have already generated keys and just want to search如果是，那么您已经生成了密钥并且只想搜索
        # if (reply.lower() == 'y' or reply.lower() == "yes"):
        #     password = input("请输入密码: ")
        #     break
        #
        # # if no then need to generate symmetric keys如果没有，则需要生成对称密钥
        # elif (reply.lower() == 'n' or reply.lower() == "no"):
        #     password = None
        #     while (True):
        #         password1 = input("请选择密码: ")
        #         password2 = input("请重新输入密码: ")
        #         if (password1 == password2):
        #             password = password1
        #             print("")
        #             break
        #         print("密码不一样再试一次\n")
        password = 'blockchaintest'
        key_s, key_y, key_z = keygen(password)
        # print('三个密钥分别为：', key_s, key_y, key_z)
        tarray = time.time()
        cutalltxt()
        word_dict = intialization()
        # print('---word_dict', word_dict)

        A, keyword_key_pair = build_array(word_dict, key_s, key_y, key_z)
        # print(btos(A))
        # print('*********************************************************************************A=')
        # print(A)
        hashA=hashlib.sha256(btos(A).encode('utf-8')).hexdigest()
        # print(hashA)
        # print(hashA)
        # for i in range(100):
        #     sendtosql(hashA,btos(A))
        # print('A,keywordpair',A, keyword_key_pair)
        # print(A)
        T = look_up_table(keyword_key_pair, key_s, key_y, key_z)
        # hashT = hashlib.sha256(btos(T).encode('utf-8')).hexdigest()
        # print('构建索引所需时间为：', time.time() - tarray)
        # print('**************************************************************************')
        # print("\n\nWelcome!")
        # keyword = input("请输入关键字进行搜索，或“exit”退出：")
        #
        # while keyword != 'exit':
        #     # ttrap=time.time()
        #     tsearch = time.time()
        #     for tt in range(10000):
        #
        #         trapdoor = Trapdoor(keyword, key_z, key_y)
        #         # print('trapdoor=',trapdoor,'构建陷门所需时间为',time.time()-ttrap)
        #
        #         list_of_docs = Search(T, A, trapdoor)
        #     print('进行%d次查找所需时间为'%(tt+1), time.time() - tsearch)
        #     print(f"\n为关键词 \"{keyword}\"寻找结果:\n")
        #     for i in list_of_docs:
        #         print(i)
        #
        #     keyword = input("\n请输入关键字进行搜索，或“exit”退出： ")
        #
        # print("\n\n再见!\n")

        # break
        return T,A,word_dict,hashA

def btos(bytelist):
    strlist = [x.decode('utf-8') for x in bytelist]
    return ','.join(strlist)

def stob(strlist):
    bytelist = [x.encode('utf-8') for x in strlist]
    return bytelist
    # # this just makes sure user enters yes or no or y or n
    # else:
    #     reply = input("\n输入 Y 表示是或 N 表示否: ")


############################################################################################
def get_all_filenames():
    path = r'D:/ssetest'
    filenamess = []
    for filename in os.listdir(path):
        fn = os.path.join(filename)
        filenamess.append(fn)
    return filenamess


def getname():
    filenames = []
    for root, dirs, files in os.walk('D:\\filecut'):
        for file in files:
            fn = os.path.join(root, file)
            filenames.append(fn)
    return filenames



def cutalltxt():
    CUR_PATH = r'D:/filecut/'
    # CUR_PATH = r'D:/test/'
    def del_file(path):
        ls = os.listdir(path)
        for i in ls:
            c_path = os.path.join(path, i)
            if os.path.isdir(c_path):
                del_file(c_path)
            else:
                os.remove(c_path)

    del_file(CUR_PATH)
    def getname():
        filenames = []
        for root, dirs, files in os.walk('D:\\ssetest'):
            for file in files:
                fn = os.path.join(file)
                filenames.append(fn)
        return filenames
    def cuttxt(fileo):
        # print(fileo)
        # with open('D:\\ssetest\\%s'%fileo, "r",encoding='UTF-8') as f:
        #     lines = f.readlines()
        #     for index, line in enumerate(lines, 1):
        #         # print (index, line)
        #         with open('D:\\filecut\\%s_%d.txt'%(i,index), 'w',encoding='UTF-8') as tmp:
        #             tmp.write(line)
        for index, line in enumerate(open('D:\\ssetest\\%s'%fileo, 'r',encoding='UTF-8'), 1):
            with open('D:\\filecut\\%s_%d.txt'%(i[:-4],index), 'w',encoding='UTF-8') as tmp:
                tmp.write(line)

    filenames=getname()
    # print(type(filenames))
    for i in filenames:
        cuttxt(i)




def intialization():
    '''
    Prompts user for documents to be encrypted and generates the distinct
    words in each. Returns the distinct words and the documents that contained them
    in a dictionary 'word_dict'提示用户对文档进行加密并在每个文档中生成不同的单词。返回字典'word_dict'中的不同单词和包含它们的文档
    '''
    # cuttxt.cutalltxt()
    filenames=getname()
    # filenames=['52.txt','51.txt']
    # print(filenames,type(filenames))
    # x = input("请输入您要加密的文件的名称: ")
    # filenames.append(x)
    # while (True):
    #     x = input("\n输入另一个文件名，如果完成，请按 Enter： ")
    #     if not x:
    #         break
    #     filenames.append(x)
    # print('待加密的文件为', filenames)
    # finds the occurence of each word in a flle查找文件中每个单词的出现
    filedata = []
    for idx, val in enumerate(filenames):
        cnt = Counter()
        for line in open(filenames[idx], 'r', encoding='UTF-8'):
            # neirong=open(filenames[idx], 'r', encoding='UTF-8')
            # print(neirong)
            word_list = line.replace(',', '').replace('\'', '').replace('.', '').lower().split()
            # print(word_list)
            for word in word_list:
                cnt[word] += 1
        filedata.append((val, cnt))

    # takes the 5 most common from each document as the distinct words将每个文档中最常见的 5 个作为不同的单词
    allwords = []
    for idx, val in enumerate(filedata):
        for value, count in val[1].most_common(7):
            if value not in allwords:
                allwords.append(value)

    # makes a dictory with the distinct word as index and a value of a list of filenames用不同的单词作为索引和文件名列表的值创建一个字典
    word_dict = defaultdict(list)

    for i in allwords:
        word_dict[i]
        for idx, val in enumerate(filedata):
            if i in val[1]:
                word_dict[i].append(val[0])
        word_dict[i].sort()
    # print('提取的关键字字典为: ', word_dict)
    return word_dict


############################################################################################

def keygen(u_password):
    ''' Generates 3 keys, key s,y,z, based on the given password from the user. 根据用户给定的密码生成 3 个密钥，密钥 s,y,z'''

    # This is input in the form of a string这是以字符串的形式输入的
    password_provided = u_password

    # Convert to type bytes转换为字节类型
    password = password_provided.encode()

    salt_s = b'\x91\xabr\xebx\xc5\x9dx^b_7\xb6\x8a\xbb5'
    salt_y = b'\x1cy8\r\x7f\xf8,\xe2Pu!/\x043\xdc\x0e'
    salt_z = b'\x9b\xd0\xb6\x85!J\xde\xe5\xc8\xb3\xc9\xa2\tqPy'

    kdf_s = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt_s,
        iterations=100000,
        backend=default_backend()
    )
    key_s = base64.urlsafe_b64encode(kdf_s.derive(password))

    kdf_y = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt_y,
        iterations=100000,
        backend=default_backend()
    )
    key_y = base64.urlsafe_b64encode(kdf_y.derive(password))

    kdf_z = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt_z,
        iterations=100000,
        backend=default_backend()
    )
    key_z = base64.urlsafe_b64encode(kdf_z.derive(password))

    # returns three base_64 encoded keys
    # print(type(key_z), key_z)
    return key_s, key_y, key_z


############################################################################################
def psuedo_random(key_s, ctr):
    ''' A pseudorandom function based on key s, to return a value to index array A 一个基于 key s 的伪随机函数，返回一个值到索引数组 A'''

    # Convert key s to decimal value将 key 转换为十进制值
    decimal_key = int.from_bytes(key_s, byteorder=sys.byteorder)
    combined = decimal_key + ctr

    # Find a random value based on key s and counter根据key和counter找一个随机值
    random.seed(combined)
    index = random.randrange(0, 10000)
    return index


############################################################################################
def build_array(word_dict, key_s, key_y, key_z):
    '''
    Creates an array of nodes, each containing the document id, key to encrypt the
    next node, and the address of the next node创建一个节点数组，每个节点包含文档 id、加密下一个节点的密钥和下一个节点的地
    '''
    # print('***---***---***---***---***---***-开始构建节点数组-***---***---***---***---***---***---***')
    A = [0] * 10000
    # print('A=',A)
    ctr = 1
    # print('初始化全局计数器ctr：ctr=', ctr)
    keyword_key_pair = []
    kki = 0
    kkj = 0
    # for each word in set of distinct words, word_dict in this case对于一组不同单词中的每个单词，在这种情况下为 word_dict
    for i, doc_list in word_dict.items():
        # print('关键字',i,'所在文档',doc_list)
        # Generate a key for the first node为第一个节点生成密钥
        K_i_0 = Fernet.generate_key()  # 加解密算法为AES，密钥位长128，CBC模式，填充标准PKCS7
        # print('---K_i_j', K_i_0)
        # print('生成对称加密密钥K_%d_%d' %(kki,kkj))
        kki += 1  # 仅作为输出密钥序号使用
        keyword_key_pair.append([i, K_i_0, ctr])
        # print('---keyword_key_pair', keyword_key_pair)
        # initialize the previous key to the first one created将前一个键初始化为第一个创建的键
        K_i_jminus1 = K_i_0

        # for 1 <= j <= |D(wi)|:
        # for each document which has distinct word wi....iterate through doc_list对于每个具有不同单词的文档 wi ....遍历 doc_list
        for j, doc in enumerate(doc_list):
            # print('j=', j, doc)
            # print('关键字', j, '所在文档', doc_list)
            # again generate key K(i,j)再次生成密钥 K(i,j)
            K_i_j = Fernet.generate_key()
            # print('再次生成的K_i_j', K_i_j)
            # print('生成对称加密密钥K_%d_%d' %(kki,kkj))
            kkj += 1  # 仅作为输出密钥序号使用
            # N(i,j) = (id(D(i,j) || K(i,j) || v(s)(ctr+1)), where id(D(i,j) is the jth identifier in D(wi)
            # 其中 id(D(i,j) 是 D(wi) 中的第 j 个标识符

            curr_addr = psuedo_random(key_s, ctr)
            # print('current_address=', curr_addr)
            if j == len(doc_list) - 1:
                next_addr = None
            else:
                next_addr = psuedo_random(key_s, ctr + 1)

            N = doc + "\n" + str(K_i_j) + "\n" + str(next_addr)
            # print('节点N_%d_%d='%(kki,kkj),'【',doc + "，" + str(K_i_j) + "，" + str(next_addr),'】')
            # newline is a delimeter to separate three components of the encrypted string换行符是分隔加密字符串的三个组成部分的分隔符
            # N = doc + K_i_j + address of next node.N = doc + K_i_j + 下一个节点的地址。

            # encrypt N with Ki,j-1, ie the previous key用 Ki,j-1 加密 N，即前一个密钥
            N = Fernet(K_i_jminus1).encrypt(str.encode(N))
            # print('前一个密钥Ki,j-1加密N：',N)
            # update and save K at i,j-1在 i,j-1 处更新并保存 K
            K_i_jminus1 = K_i_j

            # store the encrypted N in the array将加密的 N 存储在数组中
            A[curr_addr] = N
            # print('加密后的数组A：',A)
            # update counter更新计数器
            ctr = ctr + 1
            # print('---N=', N)
        kkj = 0
    # Filling in the rest of the array with random encrypted data用随机加密数据填充数组的其余部分
    for ind, val in enumerate(A):
        if (val == 0):
            x = random.randrange(0, 10000)
            x = str(x)
            # print(x)
            x = Fernet(key_s).encrypt(str.encode(x))
            A[ind] = x
    # print('节点Node：',keyword_key_pair)
    # print(A)
    return A, keyword_key_pair


############################################################################################
def look_up_table(keyword_key_pair, key_s, key_y, key_z):
    '''
    Generates a table which stores the XORed result of permutation
    function f_y and the address of a node concatenated with the key生成一个表，该表存储置换函数 f_y 的异或结果和与键连接的节点的地址
    '''
    T = [0] * 1000
    for i in keyword_key_pair:
        # print(i)
        keyword = i[0]
        key = i[1]
        ctr = i[2]

        # pseudorandom permutation on z  ; z 上的伪随机排列
        random.seed(keyword + str(key_z))
        index = random.randrange(0, 1000)

        # computes value <addr(A(Ni,1)||K_i_0)>计算值 <addr(A(Ni,1)||K_i_0)>
        addr = psuedo_random(key_s, ctr)
        value = str(addr) + "\n" + str(key)
        # print(value)

        # computed 'f_y(w_i)'
        random.seed(keyword + str(key_y))
        f_y = random.randrange(0, 1000)

        # XOR value with f_y与 f_y 异或值
        cat_string = []  # empty string to begin
        for m in value:
            # concatenate ascii value of each character in value连接 value 中每个字符的 ascii 值
            cat_string.append(ord(m))

        value = [f_y ^ x for x in cat_string]

        T[index] = value

    # set all elements equal to zero as some random key value将所有元素设置为零作为某个随机键值
    for ind, val in enumerate(T):
        if (val == 0):
            x = random.randrange(0, 10000)
            x = str(x)
            x = Fernet(key_s).encrypt(str.encode(x))
            T[ind] = x
    # print(T)
    return T
def sendtosql(sqlhash, sqlA):
    # 打开数据库连接
    mydb = MySQLdb.connect("localhost", "root", "123456", "sse", charset='utf8')

    # mydb = mysql.connector.connect(
    #     host="localhost",
    #     user="你的用户名",
    #     passwd="你的密码",
    #     database="mydatabase"
    # )

    mycursor = mydb.cursor()

    sql = "INSERT INTO ssetest (hash,txt) VALUES (%s, %s)"
    val = (sqlhash,sqlA)
    mycursor.execute(sql, val)

    mydb.commit()

    print(mycursor.rowcount, "条记录已插入")
def Trapdoor(keyword, key_z, key_y):
    '''
    returns the permutation function
    and pseudorandom permutation function on keyword返回关键字的排列函数和伪随机排列函数
    '''
    random.seed(keyword + str(key_z))
    index = random.randrange(0, 1000)

    # the pseudo-random function 'f_y(w)'伪随机函数“f_y(w)”
    random.seed(keyword + str(key_y))
    f_y = random.randrange(0, 1000)
    # print('trapdoor-index',index)
    return (index, f_y)


def Search(T, A, trapdoor):
    '''
    Indexes both T and A with trapdoor values generated by keyword
    in main to find and decrypt the document ids.使用 main 中的关键字生成的陷门值对 T 和 A 进行索引，以查找和解密文档 ID。
    '''

    list_of_docs = []
    # print('traprood0',trapdoor[0])
    value = T[trapdoor[0]]
    # print(value)
    f_y = trapdoor[1]

    # XORs the ascii value with f_y to obtain list version of string,DOE 使用 f_y 获取 ascii 值以获取字符串的列表版本
    #	containing the address and the key for the node包含节点的地址和密钥
    addr_and_key = [chr(f_y ^ x) for x in value]
    # print('addr_and_key=',addr_and_key)
    # converts the list into one string将列表转换为一个字符串
    mystring = ''
    for x in addr_and_key:
        mystring = mystring + x
    # print('mystring',mystring)
    # addr_node is a list.addr_node 是一个列表。
    addr_node = re.split(r"\n", str(mystring))
    # print('addr_node',addr_node)
    # if addr_node isn't two separate items, we didn't find a document如果 addr_node 不是两个单独的项目，我们没有找到文档
    if len(addr_node) == 1:
        print("\n")
    else:

        addr = addr_node[0]
        key = addr_node[1]

        # remove b' at the beginning and ' at the end删除开头的 b' 和结尾的 '
        key = key[2:-1]

        # get the node based on the address from array A根据数组 A 中的地址获取节点
        node = A[int(addr)]

        # turn key back into bytes and use Fernet function to将密钥转回字节并使用 Fernet 函数
        #	decrypt back to plaintext解密回明文
        # print('解密密钥', key, '节点', node)
        # time.sleep(1)
        decrypted_node = Fernet(str.encode(key)).decrypt(node)
        # print('解密回明文',decrypted_node)
        # remove b' at the beginning and ' at the end
        d_n = str(decrypted_node)[2:-1]
        split_node = re.split(r"\\n", d_n)
        doc_id = split_node[0]
        key = split_node[1]
        addr = split_node[2]
        # print('解密后：',doc_id,key,addr)
        list_of_docs.append(doc_id)

        # Repeat iterating while the address is not null, meaning
        #	 there are still documents with the keyword
        #	 在地址不为空的情况下重复迭代，这意味着仍然有带有关键字的文档
        while addr != 'None':
            key = key[2:-1]
            key = str.encode(key)
            # time.sleep(1)
            node = A[int(addr)]
            # print('key=', key, 'node=', node)
            decrypted_node = Fernet(key).decrypt(node)
            d_n = str(decrypted_node)[2:-1]
            split_node = re.split(r"\\n", d_n)
            doc_id = split_node[0]
            key = split_node[1]
            addr = split_node[2]
            # print('解密后：', doc_id, key, addr)
            list_of_docs.append(doc_id)
    # print(list_of_docs)
    return list_of_docs


if __name__ == '__main__':
    main()
